/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.common.utils.balance.context;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.servlet.ServletContextEvent;

import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.foreveross.bsl.common.utils.balance.ServiceListener;
import com.foreveross.bsl.common.utils.balance.ZkCfg;
import com.foreveross.bsl.common.utils.balance.ZkNode;
import com.foreveross.bsl.common.utils.balance.cxf.AbstractLoadBalanceStrategy;
import com.foreveross.bsl.common.utils.balance.zookeeper2.ZKServiceKeeper;
import com.foreveross.bsl.common.utils.factory.InstanceFactory;
import com.foreveross.bsl.common.utils.factory.InstanceSpringProvider;


/** 
 * @description 针对Zookeeper集群动态化改造
 *
 * @author heyw
 * @version v1.0
 *
 * @2014-10-13
 *
 */
public class ZkContextLoaderListener extends ContextLoaderListener {
	
	private static final Logger logger = LoggerFactory.getLogger(ZkContextLoaderListener.class);
	
	/* (non-Javadoc)
	 * @see org.springframework.web.context.ContextLoaderListener#contextInitialized(javax.servlet.ServletContextEvent)
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		final ServletContextEvent event2 = event;
		
		/**
		 * step1：获取Spring容器上下文，动态注入zk全局配置引用、动态控制容器启动 */
		WebApplicationContext applicationContext = WebApplicationContextUtils.
				getRequiredWebApplicationContext(event.getServletContext());
		Resource resource = new ClassPathResource("/zookeeper.properties"); // also ResourceBundle.getBundle("/zookeeper");
		Properties props = null;
		try {
			props = PropertiesLoaderUtils.loadProperties(resource);
		} catch (IOException e) {
			logger.error("读取zookeeper配置异常！", e);
		}
		final ZkCfg zkCfg = ZkCfg.getInstance(props); // single
		applicationContext.getServletContext().setAttribute("zkCfg", zkCfg); // 怕被垃圾回收引用到全局变量
		if(zkCfg.getDependencieNodes().size() == 0) {
			super.contextInitialized(event); // step：交给Spring容器启动
			InstanceSpringProvider provider = new InstanceSpringProvider(applicationContext); // 抽象spring容器上下文
			InstanceFactory.setInstanceProvider(provider); 
			return;
		}
		if(zkCfg.getZksk() != null) {
			logger.info("所有依赖启动完毕！！！～～～");
			/**
			 * step4: 重新构造其他对象引用的Bean*/ 
			super.contextInitialized(event); // step：交给Spring容器启动
			InstanceSpringProvider provider = new InstanceSpringProvider(applicationContext); // 抽象spring容器上下文
			InstanceFactory.setInstanceProvider(provider); 
			// 添加cxf负载均衡支持, 监听所有依赖子节点
			Map<String, ServiceListener> listenerPaths = new HashMap<String, ServiceListener>();
			final List<String> list = new ArrayList<String>();
			for (String denpend : zkCfg.getDependencieNodes().keySet()) {
				final String temp = denpend;
				listenerPaths.put(zkCfg.getNodes().getPath() + ZkCfg.SEPARATOR + denpend, new ServiceListener() {
					private static final long serialVersionUID = -2218760926797983191L;

					@Override
					public void process(Map<String, String> newServiceMap) {
						logger.debug("依赖节点{}有事件到来! 该下节点数量=", temp, newServiceMap.size());
						// 动态设置负载均衡列表
						AbstractLoadBalanceStrategy strategy = InstanceFactory.getContextInstance(temp + "LoadBalanceStrategy");
						list.clear();
						list.addAll(newServiceMap.values());
						strategy.setAlternateAddresses(list);
					}
					@Override
					public void afterConnected() {}
				});
			}
			zkCfg.getZksk().listen(listenerPaths); // 监听所有依赖节点
			return;
		}
		// 获取全局配置信息
		ZkNode rootNode = zkCfg.factorZkNode(); 
		rootNode.setName(zkCfg.getRootNode()); // app
		rootNode.setPath(ZkCfg.SEPARATOR + rootNode.getName()); // /app
		
		ZkNode serviceNode = new ZkNode();
		serviceNode.setzParent(rootNode);
		serviceNode.setName(applicationContext.getServletContext().getContextPath()); // /app/contextPath
		serviceNode.setPath(rootNode.getPath() + ZkCfg.SEPARATOR + serviceNode.getName());
		
		ZkNode entry = new ZkNode();
		entry.setzParent(serviceNode);
		entry.setName(zkCfg.getHost());// /app/contextPath/URL
		entry.setPath(rootNode.getPath() + ZkCfg.SEPARATOR + serviceNode.getName() + ZkCfg.SEPARATOR + entry.getName());
		entry.setData(zkCfg.getProtocol() + "://" + zkCfg.getHost() + ":" + zkCfg.getPort() 
				+ "/" + serviceNode.getName() + zkCfg.getWsInfo());
		entry.setLast(true);
		
		serviceNode.addzChilds(entry);
		rootNode.addzChilds(serviceNode);
		
		/**
		 * step2：启动zk-client：注册服务、加载zk全局配置（包括调度策略）*/
		final ZKServiceKeeper zksk = new ZKServiceKeeper(zkCfg.getServerList(), zkCfg.getSessionTimeout());
		zkCfg.setZksk(zksk);
		zkCfg.adapterZnode(rootNode, zksk); // 适配所有app节点信息
		zksk.register(entry.getPath(), entry.getData(), CreateMode.EPHEMERAL_SEQUENTIAL); // 注册当前节点，客户端失去连接后自动删除
		
		// 添加listenner ：监听zk依赖ws服务是否启动 ，对于web来说需要等待所有的ws都启动
		zksk.listen(rootNode.getPath(), new ServiceListener() {
			private static final long serialVersionUID = 1031109637384660635L;
			
			@Override
			public void process(Map<String, String> newServiceMap) {
				logger.debug("节点{}有事件到来!", zkCfg.getNodes().getPath());
				int p = 0;
				for (String dependent : newServiceMap.keySet()) {
					// 判断zookeeper中是否已经存在依赖节点、且其下有子节点
					if(zkCfg.getDependencieNodes().containsKey(dependent) && 
							zksk.getServiceMap(zkCfg.getNodes().getPath() + ZkCfg.SEPARATOR + dependent).size() > 0 &&
							zkCfg.getDependencieNodes().get(dependent)) {
						logger.debug("此依赖节点{}启动成功!", dependent);
						p ++;
					} else if(zkCfg.getDependencieNodes().containsKey(dependent) &&
							!zkCfg.getDependencieNodes().get(dependent)) {
						zkCfg.getDependencieNodes().put(dependent, true);
					}
				}
				/**
				 * step3 直到启动所有依赖，再启动spring容器*/
				if(p == zkCfg.getDependencieNodes().size()) {
					ZkContextLoaderListener.this.contextInitialized(event2);
				}
			}
			@Override
			public void afterConnected() {
				logger.info("监听到服务器完成连接，重新注册信息并且更新监听数据。");
				zkCfg.adapterZnode(zkCfg.getNodes(), zksk);
			}
		});
	}  
}
